﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing.Drawing2D;
using System.Drawing;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;

namespace Diagram
{
    //диаграмма
    [Serializable]
    public class cDiagram
    {
        //список фигур диаграммы
        public readonly List<Figure> figures = new List<Figure>();

        //сохранение диаграммы в файл
        public void Save(string fileName)
        {
            using (FileStream fs = new FileStream(fileName, FileMode.Create))
                new BinaryFormatter().Serialize(fs, this);
        }

        //чтение диаграммы из файла
        public static cDiagram Load(string fileName)
        {
            using (FileStream fs = new FileStream(fileName, FileMode.Open))
                return (cDiagram)new BinaryFormatter().Deserialize(fs);
        }
    }

    //фигура
    [Serializable]
    public abstract class Figure
    {
        //линии фигуры
        public SerializableGraphicsPath serializablePath = new SerializableGraphicsPath();
        protected GraphicsPath Path { get { return serializablePath.path; } }
        //карандаш отрисовки линий
        public static Pen pen = Pens.Black;

        //точка находится внутри фигуры?
        public abstract bool IsInsidePoint(Point p);

        //отрисовка фигуры
        public abstract void Draw(Graphics gr);

        //получение маркеров
        public abstract List<Marker> CreateMarkers(cDiagram diagram);

        
    }

    //многоугольник с текстом внутри
    [Serializable]
    public abstract class SolidFigure : Figure
    {
        public float Xscale;
        public float Yscale;

        //размер новой фигуры, по умолчанию
        protected static int defaultSize = 40;
        //заливка фигуры
        
        public static Brush brush = Brushes.White;
        //местоположение центра фигуры
        public Point location;
        //прямоугольник, в котором расположен текст
        protected RectangleF textRect;
        //текст
        public string text = null;
        //настройки вывода текста
        protected virtual StringFormat StringFormat
        {
            get
            {
                StringFormat stringFormat = new StringFormat();
                stringFormat.Alignment = StringAlignment.Center;
                stringFormat.LineAlignment = StringAlignment.Center;
                return stringFormat;
            }
        }

        protected virtual StringFormat StringFormatMethodProperties
        {
            get
            {
                StringFormat stringFormatMethodProperties = new StringFormat();
                stringFormatMethodProperties.Alignment = StringAlignment.Near;
                stringFormatMethodProperties.LineAlignment = StringAlignment.Near;
                return stringFormatMethodProperties;
            }
        }
        //находится ли точка внутри контура?
        public override bool IsInsidePoint(Point p)
        {
            return Path.IsVisible(p.X - location.X, p.Y - location.Y);
        }

        //прямоугольник вокруг фигуры (в абсолютных координатах)
        public RectangleF Bounds
        {
            get
            {
                RectangleF bounds = Path.GetBounds();
                return new RectangleF(bounds.Left + location.X, bounds.Top + location.Y, bounds.Width, bounds.Height);
            }
        }

        //прямоугольник текста (в абсолютных координатах)
        public Rectangle TextBounds
        {
            get
            {
                return new Rectangle((int)textRect.Left + location.X, (int)textRect.Top + location.Y, (int)textRect.Width, (int)textRect.Height);
            }
        }

        //размер прямоугольника вокруг фигуры
        public SizeF Size
        {
            get { return Path.GetBounds().Size; }
            set
            {
                SizeF oldSize = Path.GetBounds().Size;
                SizeF newSize = new SizeF(Math.Max(1, value.Width), Math.Max(1, value.Height));
                //коэффициент шкалировани по x
                float kx = newSize.Width / oldSize.Width;
                //коэффициент шкалировани по y
                float ky = newSize.Height / oldSize.Height;
                if (this is ClassFigure)
                    (this as ClassFigure).cScale(kx, ky);//(float)0.5, (float)0.5);
                else
                    Scale(kx, ky);
            }
        }

        //изменение масштаба фигуры
        public void Scale(float scaleX, float scaleY)
        {
            Xscale*=scaleX;
            Yscale *= scaleY;
            //масштабируем линии
            Matrix m = new Matrix();
            m.Scale(scaleX, scaleY);
            Path.Transform(m);
            //масштабируем прямоугльник текста
            textRect = new RectangleF(textRect.Left * scaleX, textRect.Top * scaleY, textRect.Width * scaleX, textRect.Height * scaleY);
        }

        //сдвиг местоположения фигуры
        public virtual void Offset(int dx, int dy)
        {
            location.Offset(dx, dy);
            if (location.X < 0)
                location.X = 0;
            if (location.Y < 0)
                location.Y = 0;
        }

        //отрисовка фигуры
        public override void Draw(Graphics gr)
        {
            gr.TranslateTransform(location.X, location.Y);
            gr.FillPath(brush, Path);
            gr.DrawPath(pen, Path);
            if (!string.IsNullOrEmpty(text))
                gr.DrawString(text, SystemFonts.DefaultFont, Brushes.Black, textRect, StringFormat);
            gr.ResetTransform();
        }

        //создание маркера для изменения размера
        public override List<Marker> CreateMarkers(cDiagram diagram)
        {
            List<Marker> markers = new List<Marker>();
            Marker m = new SizeMarker();
            m.targetFigure = this;
            markers.Add(m);

            return markers;
        }
    }

    //прямоугольник
    [Serializable]
    public class RectFigure : SolidFigure
    {

        public RectFigure()
        {
            Path.AddRectangle(new RectangleF(-defaultSize, -defaultSize / 2, 2 * defaultSize, defaultSize));
            
            textRect = new RectangleF(-defaultSize + 3, -defaultSize / 2 + 2, 2 * defaultSize - 6, defaultSize - 4);
        }
        

    }
    //прямоугольник
    [Serializable]
    public class CommentFigure : SolidFigure
    {

        public CommentFigure()
        {
            Xscale = 1;
            Yscale = 1;
            Path.AddRectangle(new RectangleF(-defaultSize, -defaultSize / 2, 2 * defaultSize, defaultSize));
            Path.AddLine(new PointF(Path.GetBounds().Left + 3 * Path.GetBounds().Width / 4, Path.GetBounds().Top),
                         new PointF(Path.GetBounds().Left + Path.GetBounds().Width, Path.GetBounds().Top + Path.GetBounds().Height / 4));
            textRect = new RectangleF(-defaultSize + 3, -defaultSize / 2 + 2, 2 * defaultSize - 6, defaultSize - 4);
        }
    }

    [Serializable]
    public class PaketFigure : SolidFigure
    {
        public PaketFigure()
        {
            Xscale = 1;
            Yscale = 1;
            //Path.AddRectangle(new RectangleF(-defaultSize, -defaultSize / 2, defaultSize, defaultSize / 4));
            Path.AddRectangle(new RectangleF(-defaultSize, -defaultSize / 2 + defaultSize / 4, 2 * defaultSize, defaultSize));
            textRect = new RectangleF(-defaultSize + 3, -defaultSize / 2 + defaultSize / 4 + 2, 2 * defaultSize - 6, defaultSize - 4);
            
        }
        //отрисовка фигуры
        public override void Draw(Graphics gr)
        {
            GraphicsPath pathForMain = new GraphicsPath();
            pathForMain.AddRectangle(new RectangleF(Path.PathPoints[0].X, Path.PathPoints[0].Y - defaultSize / 4, defaultSize * Xscale, defaultSize / 4));

            gr.TranslateTransform(location.X, location.Y);
            gr.FillPath(brush, pathForMain);
            gr.DrawPath(pen, pathForMain);

            gr.FillPath(brush, Path);
            gr.DrawPath(pen, Path);
            if (!string.IsNullOrEmpty(text))
                gr.DrawString(text, SystemFonts.DefaultFont, Brushes.Black, textRect, StringFormat);
            gr.ResetTransform();
        }

    }

    [Serializable]
    public class ClassFigure : SolidFigure
    {
        public string nameClass = "Имя класса";
        public List<string> properties = new List<string>();
        public List<string> methods = new List<string>();


        List<RectangleF> textsRect = new List<RectangleF>();

        public ClassFigure()
        {
            Xscale=1;
            Yscale=1;
            text = "Имя класса";
            Path.AddRectangle(new RectangleF(-defaultSize, -defaultSize / 2, 2 * defaultSize, defaultSize));
            textRect = new RectangleF(-defaultSize + 3, -defaultSize / 2 + 2, 2 * defaultSize - 6, defaultSize - 4);

            Path.AddRectangle(new RectangleF(-defaultSize, defaultSize / 2, 2 * defaultSize, defaultSize / 2));
            properties.Add("+ Свойство:");
            textsRect.Add(new RectangleF(-defaultSize + 3, defaultSize / 2 + 2, 2 * defaultSize - 6, defaultSize / 2 - 4));

            Path.AddRectangle(new RectangleF(-defaultSize, defaultSize / 2 + defaultSize / 2, 2 * defaultSize, defaultSize / 2));
            methods.Add("+ Метод ()");
            textsRect.Add(new RectangleF(-defaultSize + 3, defaultSize / 2 + defaultSize / 2 + 2, 2 * defaultSize - 6, defaultSize / 2 - 4));
        }
       

        // удаление свойства фигуры "Класс"
        public void DeleteProperty(Point p)
        {

            Point pLocal = new Point(p.X - location.X, p.Y - location.Y);

            for (int i = 0; i < properties.Count; i++)
            {
                if (textsRect[i].Contains(pLocal))
                {
                    int CountRectProp = 1;
                    int CountRectMet = 1;

                    properties.RemoveAt(i);
                    if (properties.Count!=0)
                        textsRect.RemoveAt(textsRect.Count - 1);

                    if (properties.Count != 0)
                        CountRectProp = properties.Count;
                    if (methods.Count != 0)
                        CountRectMet = methods.Count;

                    PointF[] pPoints = Path.PathPoints; // сохраняем координаты предыдущего Path


                    if (pPoints != null)
                    {
                        // Затираем текущий Path
                        Path.Reset();

                        // Добавляем в Path прямоугольник для вывода заголовка класса
                        Path.AddRectangle(new RectangleF(pPoints[0].X, pPoints[0].Y, pPoints[1].X - pPoints[0].X, pPoints[3].Y - pPoints[0].Y));


                        Path.AddRectangle(new RectangleF(pPoints[3].X,
                                                             pPoints[3].Y,
                                                             pPoints[1].X - pPoints[0].X,
                                                             CountRectProp * (pPoints[3].Y - pPoints[0].Y) / 2));
                        Path.AddRectangle(new RectangleF(pPoints[3].X,
                                                        pPoints[3].Y + CountRectProp * (pPoints[3].Y - pPoints[0].Y) / 2,
                                                        pPoints[1].X - pPoints[0].X,
                                                       CountRectMet * (pPoints[3].Y - pPoints[0].Y) / 2));

                    }
                    break;
                }
            }
            
        }
        // Удаление метода фигуры "Класс"
        public void DeleteMethod(Point p)
        {
            Point pLocal = new Point(p.X - location.X, p.Y - location.Y);
            int propC = 1;

            if (properties.Count != 0)
                propC = properties.Count;

            for (int i = 0; i < methods.Count; i++)
            {
                if (textsRect[i + propC].Contains(pLocal))
                {
                    int CountRectProp = 1;
                    int CountRectMet = 1;
                                    
                    methods.RemoveAt(i);
                    textsRect.RemoveAt(textsRect.Count - 1);

                    if (properties.Count != 0)
                        CountRectProp = properties.Count;
                    if (methods.Count != 0)
                        CountRectMet = methods.Count;

                    PointF[] pPoints = Path.PathPoints; // сохраняем координаты предыдущего Path


                    if (pPoints != null)
                    {
                        // Затираем текущий Path
                        Path.Reset();

                        // Добавляем в Path прямоугольник для вывода заголовка класса
                        Path.AddRectangle(new RectangleF(pPoints[0].X, pPoints[0].Y, pPoints[1].X - pPoints[0].X, pPoints[3].Y - pPoints[0].Y));


                        Path.AddRectangle(new RectangleF(pPoints[3].X,
                                                             pPoints[3].Y,
                                                             pPoints[1].X - pPoints[0].X,
                                                             CountRectProp * (pPoints[3].Y - pPoints[0].Y) / 2));
                        Path.AddRectangle(new RectangleF(pPoints[3].X,
                                                        pPoints[3].Y + CountRectProp * (pPoints[3].Y - pPoints[0].Y) / 2,
                                                        pPoints[1].X - pPoints[0].X,
                                                        CountRectMet * (pPoints[3].Y - pPoints[0].Y) / 2));

                    }
                    break;
                }
            }
        }
        //прямоугольник текста (в абсолютных координатах)
        public Rectangle TextsBounds(Point p)
        {
            Point pLocal = new Point(p.X - location.X, p.Y - location.Y);

            if (textRect.Contains(pLocal))
                return new Rectangle((int)textRect.Left + location.X, (int)textRect.Top + location.Y, (int)textRect.Width, (int)textRect.Height);


            for (int i = 0; i < textsRect.Count; i++)
            {
                if (textsRect[i].Contains(pLocal))
                    return new Rectangle((int)textsRect[i].Left + location.X, (int)textsRect[i].Top + location.Y, (int)textsRect[i].Width, (int)textsRect[i].Height);
            }

            return TextBounds;
        }
        public void copyClass(ClassFigure clF)
        {
            this.properties.Clear();
            this.methods.Clear();
            this.nameClass = clF.nameClass;
            this.text = clF.text;
            for (int i = 0; i < clF.properties.Count(); i++)
                this.properties.Add(clF.properties[i]);
            for (int i = 0; i < clF.methods.Count(); i++)
                this.methods.Add(clF.methods[i]);
        }

        // Поиск текста для передачи в текстбокс редактирования
        public string Text(Point p)
        {
            Point pLocal = new Point(p.X - location.X, p.Y - location.Y);

            if (textRect.Contains(pLocal))
                return text;
            else
                for (int i = 0; i < textsRect.Count; i++)
                {
                    if (textsRect[i].Contains(pLocal))
                    {
                        if (i < properties.Count)
                            return properties[i];
                        else
                            return methods[i - properties.Count];
                    }
                }
            return null;
        }
       
        // Сохраняем отредактированный текст в нужное метод или свойство.
        public void SaveText(string text, Point p)
        {
            Point pLocal = new Point(p.X - location.X, p.Y - location.Y);

            if (textRect.Contains(pLocal))
                this.text = text;
            else
                for (int i = 0; i < textsRect.Count; i++)
                {
                    if (textsRect[i].Contains(pLocal))
                    {
                        if (i < properties.Count)
                            properties[i] = text;
                        else
                            methods[i - properties.Count] = text;
                    }
                }
        }

        // Масштабирование класса
        public void cScale(float scaleX, float scaleY)
        {
            Xscale *= scaleX;
            Yscale *= scaleY;
            //масштабируем линии
            Matrix m = new Matrix();
            m.Scale(scaleX, scaleY);
            Path.Transform(m);

            //масштабируем прямоугольники текста
            textRect = new RectangleF(Path.PathPoints[0].X + 3, Path.PathPoints[0].Y + 2, Path.PathPoints[1].X -  Path.PathPoints[0].X - 6, Path.PathPoints[3].Y - Path.PathPoints[0].Y - 4);
            
            for (int j = 0; j < textsRect.Count; j++)
            {
                textsRect[j] = new RectangleF(Path.PathPoints[3].X + 3,
                                                 Path.PathPoints[3].Y + j * (Path.PathPoints[3].Y - Path.PathPoints[0].Y) / 2 + 2,
                                                 Path.PathPoints[1].X - Path.PathPoints[0].X - 6,
                                                 (Path.PathPoints[3].Y - Path.PathPoints[0].Y) / 2 - 4);
            }
        }
        //отрисовка фигуры
        public override void Draw(Graphics gr)
        {
            gr.TranslateTransform(location.X, location.Y);

            gr.FillPath(brush, Path);
            gr.DrawPath(pen, Path);

            gr.DrawString(text, SystemFonts.DefaultFont, Brushes.Black, textRect, StringFormat);
            int i, j;
            for (i = 0; i < properties.Count; i++)
            {
                gr.DrawString(properties[i], SystemFonts.DefaultFont, Brushes.Black, textsRect[i], StringFormatMethodProperties);
            }
            if (properties.Count == 0)
                i = 1;
            for (j = 0; j < methods.Count; j++)
            {
                gr.DrawString(methods[j], SystemFonts.DefaultFont, Brushes.Black, textsRect[i + j], StringFormatMethodProperties);
            }

            gr.ResetTransform();
        }

        public void InitializeRect()
        {
            PointF[] pPoints = Path.PathPoints; // сохраняем координаты предыдущего Path

            int CountRectProp = 1;
            int CountRectMet = 1;

            // Затираем текущий Path
            Path.Reset();

            if (properties.Count != 0)
                CountRectProp = properties.Count;
            if (methods.Count != 0)
                CountRectMet = methods.Count;

            // Добавляем в Path прямоугольник для вывода заголовка класса
            Path.AddRectangle(new RectangleF(pPoints[0].X, pPoints[0].Y, pPoints[1].X - pPoints[0].X, pPoints[3].Y - pPoints[0].Y));

            Path.AddRectangle(new RectangleF(pPoints[3].X,
                                                 pPoints[3].Y,
                                                 pPoints[1].X - pPoints[0].X,
                                                 CountRectProp * (pPoints[3].Y - pPoints[0].Y) / 2));

            Path.AddRectangle(new RectangleF(pPoints[3].X,
                                            pPoints[3].Y + CountRectProp * (pPoints[3].Y - pPoints[0].Y) / 2,
                                            pPoints[1].X - pPoints[0].X,
                                           CountRectMet * (pPoints[3].Y - pPoints[0].Y) / 2));
            textsRect.Clear();
            int i;
            for (i = 0; i < properties.Count; i++)
            {
                if (pPoints != null)
                {
                    textsRect.Add(new RectangleF(pPoints[3].X + 3,
                                                 pPoints[3].Y + i * (pPoints[3].Y - pPoints[0].Y) / 2 + 2,
                                                 pPoints[1].X - pPoints[0].X - 6,
                                                 (pPoints[3].Y - pPoints[0].Y) / 2 - 4));
                }
            }

            if (properties.Count == 0)
            {
                i = 1;
                textsRect.Add(new RectangleF(pPoints[3].X + 3,
                                                 pPoints[3].Y + i * (pPoints[3].Y - pPoints[0].Y) / 2 + 2,
                                                 pPoints[1].X - pPoints[0].X - 6,
                                                 (pPoints[3].Y - pPoints[0].Y) / 2 - 4));
            }

            for (int j = 0; j < methods.Count; j++)
            {
                if (pPoints != null)
                {

                    textsRect.Add(new RectangleF(pPoints[3].X + 3,
                                                 pPoints[3].Y + (i + j) * (pPoints[3].Y - pPoints[0].Y) / 2 + 2,
                                                 pPoints[1].X - pPoints[0].X - 6,
                                                 (pPoints[3].Y - pPoints[0].Y) / 2 - 4));
                }

            }
            //масштабируем линии
            //Matrix m = new Matrix();
            //m.Scale(Xscale, Yscale);
            //Path.Transform(m);

            //масштабируем прямоугольники текста
            textRect = new RectangleF(Path.PathPoints[0].X + 3, Path.PathPoints[0].Y + 2, Path.PathPoints[1].X - Path.PathPoints[0].X - 6, Path.PathPoints[3].Y - Path.PathPoints[0].Y - 4);

            for (int j = 0; j < textsRect.Count; j++)
            {
                textsRect[j] = new RectangleF(Path.PathPoints[3].X + 3,
                                                 Path.PathPoints[3].Y + j * (Path.PathPoints[3].Y - Path.PathPoints[0].Y) / 2 + 2,
                                                 Path.PathPoints[1].X - Path.PathPoints[0].X - 6,
                                                 (Path.PathPoints[3].Y - Path.PathPoints[0].Y) / 2 - 4);
            }
        }
        // Инициализация класса 
        public void Initialize() //готово
        {
            
            methods.RemoveAt(0);
            properties.RemoveAt(0);
            InitializeRect();

            
        }
        // Добавление нового метода в класс
        public void AddMethod()
        {
            int CountRectProp = 1;
            int CountRectMet = 1;
            PointF[] pPoints = Path.PathPoints; // сохраняем координаты предыдущего Path

            methods.Add("+ Метод ()");
            if (properties.Count != 0)
                CountRectProp = properties.Count;
            if (methods.Count != 0)
                CountRectMet = methods.Count;

            if (pPoints != null)
            {
                // Затираем текущий Path
                Path.Reset();

                // Добавляем в Path прямоугольник для вывода заголовка класса
                Path.AddRectangle(new RectangleF(pPoints[0].X, pPoints[0].Y, pPoints[1].X - pPoints[0].X, pPoints[3].Y - pPoints[0].Y));

                // Добавляем в Path прямоугольники для вывода свойств и методов
                                  
                Path.AddRectangle(new RectangleF(pPoints[3].X, 
                                                     pPoints[3].Y, 
                                                     pPoints[1].X - pPoints[0].X,
                                                     CountRectProp * (pPoints[3].Y - pPoints[0].Y) / 2));
                 Path.AddRectangle(new RectangleF(pPoints[3].X,
                                                 pPoints[3].Y + CountRectProp * (pPoints[3].Y - pPoints[0].Y) / 2,
                                                 pPoints[1].X - pPoints[0].X,
                                                 CountRectMet * (pPoints[3].Y - pPoints[0].Y) / 2));

                 textsRect.Add(new RectangleF(pPoints[3].X + 3,
                                                 pPoints[3].Y + (CountRectProp + CountRectMet - 1) * (pPoints[3].Y - pPoints[0].Y) / 2 + 2,
                                                 pPoints[1].X - pPoints[0].X - 6,
                                                 (pPoints[3].Y - pPoints[0].Y) / 2 - 4));
            }


        }
       
        // Добавление нового свойства в класс
        public void AddProperty()
        {
            int CountRectProp = 1;
            int CountRectMet = 1;
            PointF[] pPoints = Path.PathPoints; // сохраняем координаты предыдущего Path

            properties.Add("+ Свойство:");
            if (properties.Count != 0)
                CountRectProp = properties.Count;
            if (methods.Count != 0)
                CountRectMet = methods.Count;

            if (pPoints != null)
            {
                // Затираем текущий Path
                Path.Reset();
                
                // Добавляем в Path прямоугольник для вывода заголовка класса
                Path.AddRectangle(new RectangleF(pPoints[0].X, pPoints[0].Y, pPoints[1].X - pPoints[0].X, pPoints[3].Y - pPoints[0].Y));

                Path.AddRectangle(new RectangleF(pPoints[3].X,
                                                     pPoints[3].Y,
                                                     pPoints[1].X - pPoints[0].X,
                                                     CountRectProp * (pPoints[3].Y - pPoints[0].Y) / 2));
                Path.AddRectangle(new RectangleF(pPoints[3].X,
                                                pPoints[3].Y + CountRectProp * (pPoints[3].Y - pPoints[0].Y) / 2,
                                                pPoints[1].X - pPoints[0].X,
                                                CountRectMet * (pPoints[3].Y - pPoints[0].Y) / 2));
                if (properties.Count != 1)
                textsRect.Add(new RectangleF(pPoints[3].X + 3,
                                             pPoints[3].Y + (properties.Count + methods.Count - 1) * (pPoints[3].Y - pPoints[0].Y) / 2 + 2,
                                             pPoints[1].X - pPoints[0].X - 6,
                                             (pPoints[3].Y - pPoints[0].Y) / 2 - 4));
            }
        }

        
    }

 

    //соединительная линия
    [Serializable]
    public class LineFigure : Figure
    {
        public SolidFigure From;
        public SolidFigure To;
        public int fromIndex;
        public int toIndex;
        static Pen clickPen = new Pen(Color.Transparent, 3);
        //для выделения при контроле
        static Pen SelectPen = new Pen(Color.Red, 2);
        //для выделения при контроле
       public void DrawSelect(Graphics gr)
        {
            if (From == null || To == null)
                return;

            RecalcPath();
            gr.DrawPath(SelectPen, Path);
            Draw(gr);
        }
        //

        public override void Draw(Graphics gr)
        {
            if (From == null || To == null)
                return;

            RecalcPath();
            gr.DrawPath(pen, Path);
        }

        public override bool IsInsidePoint(Point p)
        {
            if (From == null || To == null)
                return false;

            RecalcPath();
            return Path.IsOutlineVisible(p, clickPen);
        }

        protected virtual void RecalcPath()
        {
            PointF[] points = null;
            if (Path.PointCount > 0)
                points = Path.PathPoints;
            if (Path.PointCount != 2 || points[0] != From.location || points[1] != To.location)
            {
                Path.Reset();
                Path.AddLine(From.location, To.location);
            }
        }

        public override List<Marker> CreateMarkers(cDiagram diagram)
        {
            List<Marker> markers = new List<Marker>();
            EndLineMarker m1 = new EndLineMarker(diagram, 0);
            m1.targetFigure = this;
            EndLineMarker m2 = new EndLineMarker(diagram, 1);
            m2.targetFigure = this;

            markers.Add(m1);
            markers.Add(m2);

            return markers;
        }
    }

    //линия с "переломом"
    [Serializable]
    public class LedgeLineFigure : LineFigure
    {
        //координата X точки "перелома"
        internal float ledgePositionX = -1;
        //местоположение центра фигуры
        public Point location;
        //прямоугольник, в котором расположен текст
        protected RectangleF textRect;
        //текст
        public string text = null;

        protected override void RecalcPath()
        {
            PointF[] points = null;

            if (ledgePositionX < 0)
                ledgePositionX = (From.location.X + To.location.X) / 2;

            if (Path.PointCount > 0)
                points = Path.PathPoints;
            if (Path.PointCount != 4 || points[0] != From.location || points[3] != To.location ||
                points[1].X != ledgePositionX)
            {
                Path.Reset();
                Path.AddLines(new PointF[]{
                    From.location,
                    new PointF(ledgePositionX, From.location.Y),
                    new PointF(ledgePositionX, To.location.Y),
                    To.location
                    });
            }
        }

        public override List<Marker> CreateMarkers(cDiagram diagram)
        {
            RecalcPath();
            List<Marker> markers = new List<Marker>();
            EndLineMarker m1 = new EndLineMarker(diagram, 0);
            m1.targetFigure = this;
            EndLineMarker m2 = new EndLineMarker(diagram, 1);
            m2.targetFigure = this;
            LedgeMarker m3 = new LedgeMarker();
            m3.targetFigure = this;
            m3.UpdateLocation();

            markers.Add(m1);
            markers.Add(m2);
            markers.Add(m3);

            return markers;
        }
        //прямоугольник текста (в абсолютных координатах)
        //public Rectangle TextBounds
        //{
        //    get
        //    {
        //        return new Rectangle((int)textRect.Left + location.X, (int)textRect.Top + location.Y, (int)textRect.Width, (int)textRect.Height);
        //    }
        //}
    }

    [Serializable]
    public class ArrowLine : LedgeLineFigure

    {
        public int type; // тип стрелки
        public int typeA;   //атрибут, стереотип, кратность
        public string stereotype = "";
        public string attribute = "";
      //  public string multiplicityLeft = "";
        //public string multiplicityRight = "";
        RectangleF RectSter;
        RectangleF RectAtr;
        //RectangleF RectMuL;
        //RectangleF RectMuR;
        //string pattern = @"^<<?(.+)>>?$";
        string str = " ";
        string strM = "";
        //Regex rgx = new Regex(@"([^><]+)", RegexOptions.IgnoreCase);
        Regex rgx = new Regex(@"(<|>|\s)+", RegexOptions.IgnoreCase);
        Regex rgxA = new Regex(@"(\+|\s)+", RegexOptions.IgnoreCase);
        //Regex rgxM = new Regex(@"([^0-9\*])+", RegexOptions.IgnoreCase);

        public void copyLine(ArrowLine arrl)
        {
            From = arrl.From;
            To = arrl.To;
            fromIndex = arrl.fromIndex;
            toIndex = arrl.toIndex;
            ledgePositionX = arrl.ledgePositionX;
            type = arrl.type;
            typeA = arrl.typeA;
            stereotype = arrl.stereotype;
            attribute = arrl.attribute;
            //multiplicityLeft = arrl.multiplicityLeft;
            //multiplicityRight = arrl.multiplicityRight;

        }
        public void recalc()
        {
            RecalcPath();
        }
        public override void Draw(Graphics gr)
        {
            if (From == null || To == null)
                return;

            RecalcPath();
            Pen p = new Pen(Color.Black);
            if (type == 1)
            {
                
                GraphicsPath hPath = new GraphicsPath();

                hPath.AddLine(new Point(-10, -10), new Point(0, 0));
                hPath.AddLine(new Point(0, 0), new Point(10, -10));
              

                CustomLineCap HookCap = new CustomLineCap(null, hPath);

                p.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
                p.CustomEndCap = HookCap;
            }
            else if (type == 2)
            {
                
                GraphicsPath hPath = new GraphicsPath();

                hPath.AddLine(new Point(-10, -10), new Point(0, 0));
                hPath.AddLine(new Point(0, 0), new Point(10, -10));
                

                CustomLineCap HookCap = new CustomLineCap(null, hPath);

                p.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
                p.CustomEndCap = HookCap;
                p.CustomStartCap = HookCap;
            }
            else if (type == 3)
            {
                GraphicsPath hPath = new GraphicsPath();
              
                hPath.AddLine(new Point(-10, -10), new Point(0, 0));
                hPath.AddLine(new Point(0, 0), new Point(10, -10));
                hPath.AddLine(new Point(10, -10), new Point(-10, -10));

                CustomLineCap HookCap = new CustomLineCap(null, hPath);

                p.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
                p.CustomEndCap = HookCap;

            }
            else if (type == 4)
            {
                

                p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
                p.DashPattern = new float[] { 4.0F, 4.0F, 4.0F, 4.0F };
               
                GraphicsPath hPath = new GraphicsPath();

                hPath.AddLine(new Point(-10, -10), new Point(0, 0));
                hPath.AddLine(new Point(0, 0), new Point(10, -10));
                

                CustomLineCap HookCap = new CustomLineCap(null, hPath);

                p.CustomEndCap = HookCap;
            }
            else if (type == 5)
            {

                p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
                p.DashPattern = new float[] { 4.0F, 4.0F, 4.0F, 4.0F };

            }
            else if (type == 6)
            {
                GraphicsPath hPath = new GraphicsPath();

                hPath.AddLine(new Point(-6, -10), new Point(0, 0));
                hPath.AddLine(new Point(0, 0), new Point(6, -10));
                hPath.AddLine(new Point(6, -10), new Point(0, -20));
                hPath.AddLine(new Point(-6, -10), new Point(0, -20));

                CustomLineCap HookCap = new CustomLineCap(null, hPath);

                p.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
                p.CustomEndCap = HookCap;

            }
           
            gr.DrawPath(p, Path);
            if (typeA == 1)
            {
                if (stereotype != "<<>>" && stereotype != "")
                {
                    stereotype = "<<" + rgx.Replace(stereotype, str) + ">>"; //клеим скобочки

                    //StringFormat stringFormat = new StringFormat();
                    //stringFormat.Alignment = StringAlignment.Near;
                    //stringFormat.LineAlignment = StringAlignment.Near;
                    float coord = From.location.Y - To.location.Y;

                    //RectSter = new RectangleF(ledgePositionX, To.location.Y, 100, 15);
                    //gr.DrawString(stereotype, SystemFonts.DefaultFont, Brushes.Black, RectSter, stringFormat);
                }
            }
            else if (typeA == 2)
            {
                if (attribute != "+" && attribute != "")
                {
                    attribute = "+" + rgxA.Replace(attribute, str); //клеим плюсик

                    //StringFormat stringFormat = new StringFormat();
                    //stringFormat.Alignment = StringAlignment.Near;
                    //stringFormat.LineAlignment = StringAlignment.Near;
                    float coord = From.location.Y - To.location.Y;

                    //RectAtr = new RectangleF(ledgePositionX, To.location.Y - 20, 100, 15);
                    
                }
            }
            //else if (typeA == 3)
            //{
            //    if (multiplicityLeft != "")
            //    {
            //        multiplicityLeft = rgxM.Replace(multiplicityLeft, strM); //клеим плюсик
            //        float coord = From.location.Y - To.location.Y;
            //    }
            //}
            //else if (typeA == 4)
            //{
            //    if (multiplicityRight != "")
            //    {
            //        multiplicityRight =rgxM.Replace(multiplicityRight, strM); //клеим плюсик
            //        float coord = From.location.Y - To.location.Y;
            //    }
            //}
            if (typeA != 0)
            {
                StringFormat stringFormat = new StringFormat();
                stringFormat.Alignment = StringAlignment.Near;
                stringFormat.LineAlignment = StringAlignment.Near;
                RectAtr = new RectangleF(ledgePositionX, To.location.Y - 20, 100, 15);
                RectSter = new RectangleF(ledgePositionX, To.location.Y, 100, 15);
                //RectMuL = new RectangleF(From.location.X+60, From.location.Y - 20, 100, 15);
                //RectMuR = new RectangleF(To.location.X-70, To.location.Y - 20, 100, 15);
                
                gr.DrawString(stereotype, SystemFonts.DefaultFont, Brushes.Black, RectSter, stringFormat);
                gr.DrawString(attribute, SystemFonts.DefaultFont, Brushes.Black, RectAtr, stringFormat);
                //gr.DrawString(multiplicityLeft, SystemFonts.DefaultFont, Brushes.Black, RectMuL, stringFormat);
                //gr.DrawString(multiplicityRight, SystemFonts.DefaultFont, Brushes.Black, RectMuR, stringFormat);
               
            }
        }
        // Поиск текста для передачи в текстбокс редактирования
        public string Text(Point p)
        {
            if (typeA == 1)
            {
                return stereotype;
            }
            else if (typeA == 2)
            {
                return attribute;
            }
            //else if (typeA == 3)
            //{
            //    return multiplicityLeft;
            //}
            //else if (typeA == 4)
            //{
            //    return multiplicityRight;
            //}
            else return null;
        }
        public void SaveText(string text, Point p)
        {
            Point pLocal = new Point(p.X - (From.location.X - To.location.X), p.Y - (From.location.Y - To.location.Y));          
            if (typeA == 1)
            {
                this.stereotype = text;
            }
            else if (typeA == 2)
            {
                this.attribute = text;
            }
            //else if (typeA == 3)
            //{
            //    this.multiplicityLeft = text;
            //}
            //else if (typeA == 4)
            //{
            //    this.multiplicityRight = text;
            //}
        }
        protected override void RecalcPath()
        {
            PointF[] points = null;

            if (ledgePositionX < 0)
                ledgePositionX = (From.location.X + To.location.X) / 2;

            if (Path.PointCount > 0)
                points = Path.PathPoints;
            if (Path.PointCount != 4 || points[0] != From.location || points[3] != To.location ||
                points[1].X != ledgePositionX)
            {
                Path.Reset();
                if (ledgePositionX == From.location.X)
                {
                    Path.AddLine(new PointF(From.location.X,From.Bounds.Top + From.Bounds.Height), new PointF(ledgePositionX, From.location.Y));
                    Path.AddLine(new PointF(ledgePositionX, From.location.Y), new PointF(ledgePositionX, To.location.Y));
                    Path.AddLine(new PointF(ledgePositionX, To.location.Y), new PointF(ledgePositionX, To.Bounds.Top));
                }
                else if (ledgePositionX > To.location.X)
                {
                    Path.AddLine(new PointF(From.Bounds.Left + From.Bounds.Width, From.location.Y), new PointF(ledgePositionX, From.location.Y));
                    Path.AddLine(new PointF(ledgePositionX, From.location.Y), new PointF(ledgePositionX, To.location.Y));
                    Path.AddLine(new PointF(ledgePositionX, To.location.Y), new PointF(To.Bounds.Left + To.Bounds.Width, To.location.Y));
                }
                else if (ledgePositionX < From.location.X)
                {
                    Path.AddLine(new PointF(From.Bounds.Left, From.location.Y), new PointF(ledgePositionX, From.location.Y));
                    Path.AddLine(new PointF(ledgePositionX, From.location.Y), new PointF(ledgePositionX, To.location.Y));
                    Path.AddLine(new PointF(ledgePositionX, To.location.Y), new PointF(To.Bounds.Left , To.location.Y));
                }
                else
                {
                    Path.AddLine(new PointF(From.Bounds.Left + From.Bounds.Width, From.location.Y), new PointF(ledgePositionX, From.location.Y));
                    Path.AddLine(new PointF(ledgePositionX, From.location.Y), new PointF(ledgePositionX, To.location.Y));
                    Path.AddLine(new PointF(ledgePositionX, To.location.Y), new PointF(To.Bounds.Left, To.location.Y));
                }
                
                //Path.AddLines(new PointF[]{
                //    From.location,
                //    new PointF(ledgePositionX, From.location.Y),
                //    new PointF(ledgePositionX, To.location.Y),
                //    To.location
                //    });
                
            }
        }
    }


    //сериализуемая обертка над GraphicsPath
    [Serializable]
    public class SerializableGraphicsPath : ISerializable
    {
        public GraphicsPath path = new GraphicsPath();

        public SerializableGraphicsPath()
        {
        }

        private SerializableGraphicsPath(SerializationInfo info, StreamingContext context)
        {
            if (info.MemberCount > 0)
            {
                PointF[] points = (PointF[])info.GetValue("p", typeof(PointF[]));
                byte[] types = (byte[])info.GetValue("t", typeof(byte[]));
                path = new GraphicsPath(points, types);
            }
            else
                path = new GraphicsPath();
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (path.PointCount > 0)
            {
                info.AddValue("p", path.PathPoints);
                info.AddValue("t", path.PathTypes);
            }
        }
    }


    [Serializable]
    public abstract class Marker : SolidFigure
    {
        protected static new int defaultSize = 2;
        public Figure targetFigure;

        public override bool IsInsidePoint(Point p)
        {
            if (p.X < location.X - defaultSize || p.X > location.X + defaultSize)
                return false;
            if (p.Y < location.Y - defaultSize || p.Y > location.Y + defaultSize)
                return false;

            return true;
        }

        public override void Draw(Graphics gr)
        {
            gr.DrawRectangle(Pens.Black, location.X - defaultSize, location.Y - defaultSize, defaultSize * 2, defaultSize * 2);
            gr.FillRectangle(Brushes.Red, location.X - defaultSize, location.Y - defaultSize, defaultSize * 2, defaultSize * 2);
        }

        public abstract void UpdateLocation();
    }

    public class SizeMarker : Marker
    {
        public override void UpdateLocation()
        {
            RectangleF bounds = (targetFigure as SolidFigure).Bounds;
            location = new Point((int)Math.Round(bounds.Right) + defaultSize / 2, (int)Math.Round(bounds.Bottom) + defaultSize / 2);
        }

        public override void Offset(int dx, int dy)
        {
            base.Offset(dx, dy);
            (targetFigure as SolidFigure).Size =
                SizeF.Add((targetFigure as SolidFigure).Size, new SizeF(dx * 2, dy * 2));
        }
    }

    [Serializable]
    public class EndLineMarker : Marker
    {
        int pointIndex;
        cDiagram diagram;

        public EndLineMarker(cDiagram diagram, int pointIndex)
        {
            this.diagram = diagram;
            this.pointIndex = pointIndex;
        }

        public override void UpdateLocation()
        {
            LineFigure line = (targetFigure as LineFigure);
            if (line.From == null || line.To == null)
                return;//не обновляем маркеры оторванных концов
            //фигура, с которой связана линия
            SolidFigure figure = pointIndex == 0 ? line.From : line.To;
            location = figure.location;
        }

        public override void Offset(int dx, int dy)
        {
            base.Offset(dx, dy);

            //ищем фигуру под маркером
            SolidFigure figure = null;
            for (int i = diagram.figures.Count - 1; i >= 0; i--)
                if (diagram.figures[i] is SolidFigure && diagram.figures[i].IsInsidePoint(location))
                {
                    figure = (SolidFigure)diagram.figures[i];
                    break;
                }

            LineFigure line = (targetFigure as LineFigure);
            if (figure == null)
                figure = this;//если под маркером нет фигуры, то просто коннектим линию к самому маркеру

            //не позволяем конектится самому к себе
            if (line.From == figure || line.To == figure)
                return;
            //обновляем конекторы линии
            if (pointIndex == 0)
                line.From = figure;
            else
                line.To = figure;

        }
    }

    [Serializable]
    public class LedgeMarker : Marker
    {
        public override void UpdateLocation()
        {
            LedgeLineFigure line = (targetFigure as LedgeLineFigure);
            if (line.From == null || line.To == null)
                return;//не обновляем маркеры оторванных концов
            //фигура, с которой связана линия
            location = new Point((int)line.ledgePositionX, (int)(line.From.location.Y + line.To.location.Y) / 2);
        }

        public override void Offset(int dx, int dy)
        {
            base.Offset(dx, 0);
            (targetFigure as LedgeLineFigure).ledgePositionX += dx;
        }
    }

}
